﻿using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
using DG.Tweening;

public class GridManager : MonoBehaviour
{
    public int Height, Width;
    public float SquareSize;
    public Slot[,] Grids;
    public Slot SlotSample;
    public Sprite EmptySprite, NoneSprite;
    public GameObject PlayerO, Enemy1O, Enemy2O, Boss1O, BoxO, TreasureO;
    public Player Player;

    public AudioSource EliminateAudio;

    // Start is called before the first frame update
    void Start()
    {
        Grids = new Slot[Width, Height];
        hashTag = new bool[Width, Height];
        for(int i = 0; i < Width; i++) {
            Grids[i, 0] = Instantiate(SlotSample, transform);
            Grids[i, 0].GridPosition = new Vector2(i, 0);
            if(i > 0)
                Grids[i, 0].transform.position = Grids[i - 1, 0].transform.position + new Vector3(SquareSize, 0, 0);
        }
        for(int i = 1; i < Height; i++) {
            for(int j = 0; j < Width; j++) {
                Grids[j, i] = Instantiate(SlotSample, transform);
                Grids[j, i].GridPosition = new Vector2(j, i);
                Grids[j, i].transform.position = Grids[j, i - 1].transform.position + new Vector3(0, SquareSize, 0);
            }
        }
        SlotSample.gameObject.SetActive(false);

        Grids[0, 0].SetEmpty();
        Grids[1, 0].SetEmpty();
        Grids[2, 0].SetEmpty();
        Grids[4, 0].SetEmpty();
        Grids[5, 0].SetEmpty();
        Grids[6, 0].SetEmpty();
        Grids[7, 0].SetEmpty();
        Grids[10, 0].SetEmpty();
        Grids[11, 0].SetEmpty();

        Grids[0, 1].SetEmpty();
        Grids[1, 1].SetEmpty();
        Grids[5, 1].SetEmpty();
        Grids[6, 1].SetEmpty();
        Grids[7, 1].SetEmpty();
        Grids[8, 1].SetEmpty();
        Grids[10, 1].SetEmpty();
        Grids[11, 1].SetEmpty();

        Grids[1, 2].SetEmpty();

        Grids[6, 0].SetPlayer(PlayerO);
        Player.PlayerObject = Grids[6, 0].SlotObject as PlayerObject;
        Grids[1, 0].SetTreasure(TreasureO);
        Grids[10, 1].SetTreasure(TreasureO);
        Grids[7, 0].SetBox(BoxO);
        Grids[10, 0].SetBox(BoxO);

        Application.targetFrameRate = 60;
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private bool[,] hashTag;
    private int[,] go = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    private struct bfsNode1 {
        public int dir;
        public int x,y;
        public bfsNode1(int X, int Y, int Dir) {
            x = X; y = Y; dir = Dir;
        }
    }
    public Tuple<int, int> MoveToPlayer1(EnemyObject nowEnemy) {
        Array.Clear(hashTag, 0, hashTag.Length);
        int bx = (int)(nowEnemy.slot.GridPosition.x + 0.1f);
        int by = (int)(nowEnemy.slot.GridPosition.y + 0.1f);
        //int tx = (int)(Player.PlayerObject.slot.GridPosition.x + 0.1f);
        //int ty = (int)(Player.PlayerObject.slot.GridPosition.y + 0.1f);
        Vector2 posBefore = Player.PosBefore;
        Queue<bfsNode1> q = new Queue<bfsNode1>();
        q.Enqueue(new bfsNode1(bx, by, -1));
        hashTag[bx, by] = true;
        while(q.Count > 0) {
            bfsNode1 now = q.Dequeue();
            for(int i = 0; i < 4; i++) {
                int nx = now.x + go[i, 0], ny = now.y + go[i, 1];
                Vector2 nextStep = new Vector2(nx, ny);
                if(nx < 0 || nx >= Width || ny < 0 || ny >= Height)
                    continue;
                if(Grids[nx, ny].SlotState != Slot.State.empty && Grids[nx, ny].SlotState != Slot.State.player)
                    continue;
                if(hashTag[nx, ny])
                    continue;
                hashTag[nx, ny] = true;
                if(posBefore == nextStep) {
                    int dir = now.dir == -1 ? i : now.dir;
                    return new Tuple<int, int>(go[dir, 0], go[dir, 1]);
                }
                else {
                    q.Enqueue(new bfsNode1(nx, ny, now.dir == -1 ? i : now.dir));
                }
            }
        }
        return new Tuple<int, int>(0, 0);
    }
    private struct bfsNode2 {
        public int dir;
        public int dis;
        public int x,y;
        public bfsNode2(int X, int Y, int Dir, int Dis) {
            x = X; y = Y; dir = Dir; dis = Dis;
        }
    }
    public Tuple<int, int> MoveToPlayer2(EnemyObject nowEnemy) {
        Array.Clear(hashTag, 0, hashTag.Length);
        int bx = (int)(nowEnemy.slot.GridPosition.x + 0.1f);
        int by = (int)(nowEnemy.slot.GridPosition.y + 0.1f);
        int tx = (int)(Player.PlayerObject.slot.GridPosition.x + 0.1f);
        int ty = (int)(Player.PlayerObject.slot.GridPosition.y + 0.1f);
        Queue<bfsNode2> q = new Queue<bfsNode2>();
        q.Enqueue(new bfsNode2(bx, by, -1, -1));
        hashTag[bx, by] = true;
        while(q.Count > 0) {
            bfsNode2 now = q.Dequeue();
            for(int i = 0; i < 4; i++) {
                for(int j = 1; j <= Height; ++j) {
                    int nx = now.x + go[i, 0] * j, ny = now.y + go[i, 1] * j;
                    if(nx < 0 || nx >= Width || ny < 0 || ny >= Height)
                        break;
                    if(Grids[nx, ny].SlotState != Slot.State.empty && Grids[nx, ny].SlotState != Slot.State.player)
                        break;
                    if(hashTag[nx, ny])
                        continue;
                    hashTag[nx, ny] = true;
                    if(nx == tx && ny == ty) {
                        int dir = now.dir == -1 ? i : now.dir;
                        int dis = now.dis == -1 ? j : now.dis;
                        return new Tuple<int, int>(go[dir, 0] * dis, go[dir, 1] * dis);
                    }
                    else {
                        q.Enqueue(new bfsNode2(nx, ny, now.dir == -1 ? i : now.dir, now.dis == -1 ? j : now.dis));
                    }
                }
            }
        }
        return new Tuple<int, int>(0, 0);
    }
    private int[,] near = {{1, 1}, {1, 0}, {1, -1}, {0, 1}, {0, -1}, {-1, 1}, {-1, 0}, {-1, -1}};
    public Tuple<int, int> MoveToPlayerBoss1(Boss1Object Boss1) {
        // 该回合为蓄力
        Array.Clear(hashTag, 0, hashTag.Length);
        int bx = (int)(Boss1.slot.GridPosition.x + 0.1f);
        int by = (int)(Boss1.slot.GridPosition.y + 0.1f);
        int tx = (int)(Player.PlayerObject.slot.GridPosition.x + 0.1f);
        int ty = (int)(Player.PlayerObject.slot.GridPosition.y + 0.1f);
        Queue<bfsNode1> q = new Queue<bfsNode1>();
        q.Enqueue(new bfsNode1(bx, by, -1));
        hashTag[bx, by] = true;
        while(q.Count > 0) {
            bfsNode1 now = q.Dequeue();
            for(int i = 0; i < 4; i++) {
                int nx = now.x + go[i, 0], ny = now.y + go[i, 1];
                Vector2 nextStep = new Vector2(nx, ny);
                if(nx < 1 || nx >= Width - 1 || ny < 1 || ny >= Height - 1)
                    continue;
                if(hashTag[nx, ny] || Grids[nx, ny].SlotState == Slot.State.none)
                    continue;
                bool flag = false;
                for(int j = 0; j < 8; j++) {
                    int px = nx + near[j, 0], py = ny + near[j, 1];
                    if(Grids[px, py].SlotState == Slot.State.none) {
                        flag = true;
                        break;
                    }
                }
                if(flag)
                    continue;
                hashTag[nx, ny] = true;

                flag = false;
                for(int j = 0; j < 8; j++) {
                    int px = nx + near[j, 0], py = ny + near[j, 1];
                    if(Grids[px, py].SlotState == Slot.State.player) {
                        flag = true;
                        break;
                    }
                }
                if(flag) {
                    int dir = now.dir == -1 ? i : now.dir;
                    return new Tuple<int, int>(go[dir, 0], go[dir, 1]);
                }
                else {
                    q.Enqueue(new bfsNode1(nx, ny, now.dir == -1 ? i : now.dir));
                }
            }
        }
        return new Tuple<int, int>(0, 0);
    }

    public int GetFallPos(TerisInstance terisInstance, int leftColumn) {
        int LDPointY = 0;
        for(int i = 0; i < terisInstance.Teris.Width; i++) {
            int ColumnHeight = -1;
            for(int j = Height - 1; j >= 0; j--) {
                if(Grids[leftColumn + i, j].SlotState != Slot.State.none) {
                    ColumnHeight = j;
                    break;
                }
            }
            LDPointY = Math.Max(LDPointY, ColumnHeight + 1 - terisInstance.Teris.VacancyHeight[i]);
        }
        return LDPointY;
    }

    public void PushTeris(TerisInstance terisInstance, int leftColumn, int LDPointY) {
        int boss1x = -1, boss1y = -1;
        for(int i = 0; i < terisInstance.Teris.SquareCount; i++) {
            int px = leftColumn + terisInstance.Teris.PosX[i] - 1;
            int py = LDPointY + terisInstance.Teris.PosY[i] - 1;
            if(py >= Height) {
                Player.GameOver();
            }
            if(terisInstance.Contents[i] == Slot.State.empty || terisInstance.Contents[i] == Slot.State.none) {
                Grids[px, py].SetEmpty();
            }
            else if(terisInstance.Contents[i] == Slot.State.enemy) {
                Grids[px, py].SetEnemy(Enemy1O);
            }
            else if(terisInstance.Contents[i] == Slot.State.enemy2) {
                Grids[px, py].SetEnemy(Enemy2O);
            }
            else if(terisInstance.Contents[i] == Slot.State.boss1) {
                boss1x = px; boss1y = py;
            }
            else if(terisInstance.Contents[i] == Slot.State.box) {
                Grids[px, py].SetBox(BoxO);
            }
            else if(terisInstance.Contents[i] == Slot.State.treasure) {
                Grids[px, py].SetTreasure(TreasureO);
            }
        }
        if(boss1x != -1)
            Grids[boss1x, boss1y].SetBoss1(Boss1O);
    }

    public void EliminateTeris(bool willSetMove = true) {
        StartCoroutine(EliminateTerisCoroutine(willSetMove));
    }

    IEnumerator EliminateTerisCoroutine(bool willSetMove) {
        bool haveBoss1 = false;
        int boss1TposY = 0;
        for(int i = 0; i < Height; i++) {
            bool flag = true;
            for(int j = 0; j < Width; j++) {
                if(Grids[j, i].SlotState == Slot.State.none) {
                    flag = false;
                    break;
                }
                if(Grids[j, i].SlotState == Slot.State.boss1) {
                    flag = false;
                    haveBoss1 = true;
                    boss1TposY = i;
                    break;
                }
            }
            if(flag) {
                Player.AddScore(100);
                EliminateAudio.Play();
                yield return StartCoroutine(EliminateCoroutine(0, i));
                FallAll(i);
                yield return new WaitForSeconds(0.3f);
                i--;
            }
        }
        if(haveBoss1)
            yield return StartCoroutine(CheckBoss1Coroutine(boss1TposY));
        //Player.StartEnemyMove();
        if(willSetMove)
            Player.SetMoveable();
    }

    IEnumerator EliminateCoroutine(int x, int y) {
        if(x >= Width)
            yield break;
        yield return new WaitForSeconds(0.1f);
        Grids[x, y].SetNone();
        yield return StartCoroutine(EliminateCoroutine(x + 1, y));
    }

    IEnumerator CheckBoss1Coroutine(int boss1TposY) {
        if(boss1TposY < 2)
            yield break;
        bool flag = true;
        for(int i = boss1TposY - 2; i <= boss1TposY; i++) {
            for(int j = 0; j < Width; j++) {
                if(Grids[j, i].SlotState == Slot.State.none) {
                    flag = false;
                    break;
                }
            }
            if(!flag)
            break;
        }
        if(flag) {
            StartCoroutine(EliminateCoroutine(0, boss1TposY - 2));
            yield return new WaitForSeconds(0.1f);
            StartCoroutine(EliminateCoroutine(0, boss1TposY - 1));
            yield return new WaitForSeconds(0.1f);
            yield return StartCoroutine(EliminateCoroutine(0, boss1TposY));
            FallAll(boss1TposY -2, 3);
            yield return new WaitForSeconds(0.3f);
        }
    }

    private void FallAll(int beginLine, int dy = 1) {
        for(int i = beginLine; i < Height - dy; i++) {
            for(int j = 0; j < Width; j++) {
                Grids[j, i].SR.sprite = Grids[j, i + dy].SR.sprite;
                Grids[j, i].SlotState = Grids[j, i + dy].SlotState;
            }
        }
        for(int i = beginLine + dy; i < Height; i++) {
            for(int j = 0; j < Width; j++) {
                if(Grids[j, i].SlotObject != null) {
                    Grids[j, i].SlotObject.PureMove(0, -dy);
                }
            }
        }
        for(int i = 1; i <= dy; i++) {
            for(int j = 0; j < Width; j++) {
                Grids[j, Height - i].SetNone();
            }
        }
    }

    public void DropTreasure(Vector2 pos) {
        int px = (int)(pos.x + 0.1f), py = (int)(pos.y + 0.1f);
        GameObject TreasureT = Instantiate(TreasureO, Grids[px, py].transform);
        TreasureT.SetActive(true);
        //TreasureT.transform.position = .position;
        int ty = -1;
        for(int i = py - 1; i >= 0; i--) {
            if(Grids[px, i].SlotState == Slot.State.empty) {
                ty = i;
                break;
            }
        }
        if(ty == -1) {
            TreasureT.transform.DOMove(Grids[px, 0].transform.position - new Vector3(0, 3, 0) * SquareSize, 0.3f).SetEase(Ease.InQuad);
        }
        else {
            TreasureT.transform.DOMove(Grids[px, ty].transform.position, 0.3f).SetEase(Ease.InQuad);
            Grids[px, ty].SlotState = Slot.State.treasure;
        }
        StartCoroutine(TreasureFallingCoroutine(TreasureT, px, ty));
    }

    IEnumerator TreasureFallingCoroutine(GameObject TreasureT, int px, int py) {
        yield return new WaitForSeconds(0.3f);
        Destroy(TreasureT);
        if(py != -1)
            Grids[px, py].SetTreasure(TreasureO);
        else
            Player.AddTeris();
    }

    public bool Boss1Destroying(Boss1Object Boss1, int dx, int dy) {
        int nx = (int)(Boss1.slot.GridPosition.x + 0.1f) + dx;
        int ny = (int)(Boss1.slot.GridPosition.y + 0.1f) + dy;
        for(int i = 0; i < 8; i++) {
            int px = nx + near[i, 0], py = ny + near[i, 1];
            if(Grids[px, py].SlotState == Slot.State.none) {
                return false;
            }
        }
        for(int i = 0; i < 8; i++) {
            int px = nx - dx + near[i, 0], py = ny - dy + near[i, 1];
            Grids[px, py].SlotState = Slot.State.empty;
        }
        for(int i = 0; i < 8; i++) {
            int px = nx + near[i, 0], py = ny + near[i, 1];
            if(Grids[px, py].SlotState != Slot.State.empty && Grids[px, py].SlotState != Slot.State.boss1) {
                StartCoroutine(SetEmptyCoroutine(Boss1, px, py, 0.3f));
            }
            Grids[px, py].SlotState = Slot.State.boss1;
        }
        return true;
    }

    IEnumerator SetEmptyCoroutine(Boss1Object Boss1, int px, int py, float duration) {
        if(Grids[px, py].SlotObject == null)
            yield break;
        yield return new WaitForSeconds(duration);
        Boss1.BeDizzy();
        Destroy(Grids[px, py].SlotObject.gameObject);
        Grids[px, py].SlotObject = null;
    }
}
